package com.easy.project.util;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.excel.write.metadata.fill.FillWrapper;
import com.easy.project.base.exception.ServiceException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

import static com.easy.project.base.result.SystemErrorCodeEnum.EXCEL_EXPORT_ERROR;

@Slf4j
@Component
@Data
public class EasyExcelUtils {


    /**
     * 填充的配置
     */
    private FillConfig fillConfig = null;


    /**
     * 导出excel
     *
     * @param fileName
     * @param sheetName
     * @param clazz
     * @param data
     * @param response
     * @param <T>
     */
    public <T> void exportExcel(String fileName, String sheetName, Class<T> clazz, List<T> data, HttpServletResponse response) {
        setResponse(fileName, Objects.requireNonNull(response));
        try {
            // doWrite 已经调用finish关闭流
            EasyExcel.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(data);
        } catch (IOException e) {
            throw new ServiceException(EXCEL_EXPORT_ERROR);
        }
    }

    /**
     * 导出excel
     *
     * @param fileName
     * @param sheetName
     * @param data
     * @param <T>
     */
    public <T> void exportExcel(String fileName, String sheetName, Class<T> clazz, List<T> data) {
        HttpServletResponse response = getCurrentHttpServletResponse();
        setResponse(fileName, Objects.requireNonNull(response));
        try {
            // doWrite 已经调用finish关闭流
            EasyExcel.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(data);
        } catch (IOException e) {
            throw new ServiceException(EXCEL_EXPORT_ERROR);
        }
    }

    /**
     * 解析excel
     *
     * @param sheetNo
     * @param inputStream
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> List<T> readExcel(int sheetNo, InputStream inputStream, Class<T> clazz) {
        List<T> res = new ArrayList<>();
        EasyExcel.read(inputStream, clazz, new PageReadListener<T>(res::addAll)).sheet(sheetNo).doRead();
        return res;
    }

    /**
     * 填充模版数据后导出 单个集合和单个对象
     *
     * @param sheetNo
     * @param fileName
     * @param inputStream
     * @param fillData
     */
    public void fillExcelTemplate(int sheetNo, String fileName, InputStream inputStream, Object... fillData) {
        HttpServletResponse response = getCurrentHttpServletResponse();
        setResponse(fileName, Objects.requireNonNull(response));
        try (ExcelWriter excelWriter = EasyExcel.write(Objects.requireNonNull(response).getOutputStream()).withTemplate(inputStream).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet(sheetNo).build();
            for (Object data : fillData) {
                excelWriter.fill(data, writeSheet);
            }
        } catch (IOException e) {
            throw new ServiceException(EXCEL_EXPORT_ERROR);
        }
    }


    /**
     * 填充模版数据后导出 多个集合
     *
     * @param sheetNo
     * @param fileName
     * @param inputStream
     * @param fillDataList 填充数据的map  key 为占位符名称前缀  list为集合数据和配置
     * @param fillData     占位符
     */
    public void fillExcelTemplate(int sheetNo, String fileName, InputStream inputStream, Map<String, FillConfigData> fillDataList, Object fillData) {
        HttpServletResponse response = getCurrentHttpServletResponse();
        setResponse(fileName, Objects.requireNonNull(response));
        try (ExcelWriter excelWriter = EasyExcel.write(Objects.requireNonNull(response).getOutputStream()).withTemplate(inputStream).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet(sheetNo).build();
            if (Objects.nonNull(fillDataList)) {
                fillDataList.forEach((k, v) -> {
                    excelWriter.fill(new FillWrapper(k, v.collection), v.fillConfig, writeSheet);
                });
            }
            if (Objects.nonNull(fillData)) {
                excelWriter.fill(fillData, writeSheet);
            }
        } catch (IOException e) {
            throw new ServiceException(EXCEL_EXPORT_ERROR);
        }
    }

    /**
     * 设置响应头
     *
     * @param fileName
     * @param response
     */
    public void setResponse(String fileName, HttpServletResponse response) {
        // 这里注意 有同学反应使用swagger 会导致各种问题，请直接用浏览器或者用postman
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
    }

    /**
     * 获取当前请求的响应
     *
     * @return
     */
    public HttpServletResponse getCurrentHttpServletResponse() {
        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse();
    }

    /**
     * 填充的配置静态内部类
     */
    @Data
    public static class FillConfigData {

        public FillConfigData(FillConfig fillConfig, Collection collection) {
            this.fillConfig = fillConfig;
            this.collection = collection;
        }

        /**
         * 集合填充的配置
         */
        private FillConfig fillConfig;

        /**
         * 数据集合
         */
        private Collection collection;

        /**
         * 构建新的对象
         * @param fillConfig
         * @param collection
         * @return
         */
        public static FillConfigData build(FillConfig fillConfig, Collection collection){
            return new FillConfigData(fillConfig,collection);
        }
    }
}
